#include "PlotWindow.h"

#define PT_PATH		0
#define PT_DROP		1
#define PT_VELOCITY	2
#define PT_HVELOCITY	3
#define PT_ENERGY	4
#define PT_WINDAGE	5

PlotWindow::PlotWindow(int w, int h, GBCSolution* Gsln, GBCSolution* GMEM1, GBCSolution* GMEM2, GBCSolution* GMEM3) : Fl_Window(w,h,"Solution Visualization Tools"){
	
	// Store a pointer to the calling solution.
	gsln=Gsln;
	mem1=GMEM1;
	mem2=GMEM2;
	mem3=GMEM3;
	
	x_click=0;
	y_click=0;
	
	begin();
	this->size_range(0,0,1600,1600);
	
	// Put a simple menu, for saving or closing the window.
	menu = new Fl_Menu_Bar(0,0,w,25,"MENU");

	Fl_Menu_Item items[]= {
	    {"&File",0,0,0,FL_SUBMENU},
		 {"Save as..",0,0,0,FL_SUBMENU | FL_MENU_INACTIVE},
		    {"PNG (.png)",0,(Fl_Callback*)cb_nothing,this,0},
			{"JPEG (.jpg)",0,(Fl_Callback*)cb_nothing,this,0},
			{"Bitmap (.bmp)",0,(Fl_Callback*)cb_nothing,this,0},
			{"Adobe PDF (.pdf)",0,(Fl_Callback*)cb_nothing,this,0},
			{0},
		  {"&Close",0,(Fl_Callback*)cb_Close,this,0},
		  {0},			
		{0}
	};
	
	menu->copy(items);
	menu->down_box(FL_NO_BOX);

	// Put in a range slider for selecting the chart's range.	
	sl_x = new Fl_Value_Slider(150,30,350,30,"Range (yds)");
	sl_x->align(FL_ALIGN_RIGHT);
	sl_x->maximum(gsln->MaxRows());
	sl_x->type(FL_HOR_NICE_SLIDER);
	sl_x->callback(cb_slUpdate,this);
	sl_x->precision(0);
	
	// Normal firearms are ok with this
	if (gsln->MaxRows() > 200) {
	     sl_x->step(10);
	     sl_x->minimum(50);
	     sl_x->value(200);
	     }
	else { // low velocity / power such as tranquiliser darts, paintballs?
	     sl_x->step(5);
	     sl_x->minimum(30);
	     sl_x->value(50);
	     }
	
	//Fl_Button* btPlot = new Fl_Button(600,40,70,30,"UPDATE");
	//btPlot->callback(cb_btPlot,this);
	
	Fl_Menu_Item guideitems[]={
	   {"None",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"1,5 MIL",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"1-5 MIL",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"1-10 MIL",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"1-5,10 MIL",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"2,10 MOA",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"2,4,6,8,10 MOA",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"2-20 MOA",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"2-10,20 MOA",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"1-5 Degrees",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"5,10,15 Degree",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"10,20,30,40,50 Digisight N750 clicks",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"10,20..90 Digisight N750 clicks",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"10,20,30,40,50 Digisight N960 clicks",0,(Fl_Callback*)cb_Guides, this, 0},
	   {"10,20..90 Digisight N960 clicks",0,(Fl_Callback*)cb_Guides, this, 0},
	   {0}
	   };
	cScopeGuides = new Fl_Choice(290,65,150,30,"Scope Guides");
	cScopeGuides->copy(guideitems);
	cScopeGuides->value(1);
	
	btMem1 = new Fl_Check_Button(590,30,20,20,"Memory 1");
	btMem2 = new Fl_Check_Button(590,50,20,20,"Memory 2");
	btMem3 = new Fl_Check_Button(590,70,20,20,"Memory 3");
	btMem1->align(FL_ALIGN_RIGHT);
	btMem2->align(FL_ALIGN_RIGHT);
	btMem3->align(FL_ALIGN_RIGHT);
	btMem1->labelcolor(FL_RED);
	btMem2->labelcolor(FL_DARK_GREEN);
	btMem3->labelcolor(FL_BLACK);
	btMem1->callback(cb_Mem1,this);
	btMem2->callback(cb_Mem2,this);
	btMem3->callback(cb_Mem2,this);
	
	if (mem1==NULL) btMem1->deactivate();
	if (mem2==NULL) btMem2->deactivate();
	if (mem3==NULL) btMem3->deactivate();
	
	if (mem1!=NULL) btMem1->tooltip(mem1->Name());
	if (mem2!=NULL) btMem2->tooltip(mem2->Name());
	if (mem3!=NULL) btMem3->tooltip(mem3->Name());
	
	Fl_Menu_Item plottypeitems[]={
	   {"Path (inches)",0,(Fl_Callback*)cb_PlotType, this, 0},
	   {"Drop (inches)",0,(Fl_Callback*)cb_PlotType, this, 0},
	   {"Velocity",0,(Fl_Callback*)cb_PlotType, this, 0},
	   {"Horizontal Velocity",0,(Fl_Callback*)cb_PlotType, this, 0},
	   {"Energy",0,(Fl_Callback*)cb_PlotType, this, 0},
	   {"Wind Drift",0,(Fl_Callback*)cb_PlotType, this, 0},
	   {0}
	   };
	cPlotType = new Fl_Choice(35,65,130,30,"Plot");
	cPlotType->copy(plottypeitems);
	cPlotType->value(0);
	
	end();
	show();
	
	
}

PlotWindow::~PlotWindow(void){

	delete menu;
	delete sl_x;
	delete btMem1;
	delete btMem2;
	delete btMem3;
	delete cScopeGuides;
	delete cPlotType;
	

}


void PlotWindow::draw(void){
	
	// Let the window draw itself first.
	Fl_Window::draw();
	
	switch (cPlotType->value()) {
	   case PT_PATH  :   PlotPath(PT_PATH);		break;
	   case PT_DROP  :   PlotPath(PT_DROP);		break;
	   case PT_ENERGY:   PlotEnergy();		break;
	   case PT_VELOCITY: PlotVelocity(PT_VELOCITY);	break;
	   case PT_HVELOCITY:PlotVelocity(PT_HVELOCITY);break;
	   case PT_WINDAGE:  PlotPath(PT_WINDAGE);	break;
	   }
}

int PlotWindow::handle(int evt){

	if (evt==FL_PUSH && Fl::event_button1()){
		x_click=Fl::event_x();
		y_click=Fl::event_y();
		this->redraw();
			
	}
	

	
	return Fl_Window::handle(evt);

}


/* 
    PlotMem()
    
    Plot the requested data from a given GBCSolution (the current solution or a memory)
    
    mode = 0: Path, 1: Energy, 2: Velocity, 3: Wind Drift

*/

void PlotWindow::PlotMem(GBCSolution* mem, int mode){

  int x1,x2;
  double y1,y2;
  int px1, px2, py1,py2;
  
  x1=(int)mem->GetRange(0);
  switch (mode) {
	case PT_PATH:
		y1=mem->GetPath(0);
		break;
	case PT_DROP:
		y1=mem->GetDrop(0);
		break;
	case PT_ENERGY:
		y1=mem->GetEnergy(0);
		break;
	case PT_VELOCITY:
		y1=mem->GetVelocity(0);
		break;
	case PT_HVELOCITY:
		y1=mem->GetVx(0);
		break;
	case PT_WINDAGE:
		y1=mem->GetWindage(0);
		break;
  }

  int m=this->gsln->MaxRows();

  for (int n=1;n<x_range && n<m ;n++){
	x2=(int)mem->GetRange(n);
	switch (mode) {
		case PT_PATH:
			y2=mem->GetPath(n);
			break;
		case PT_DROP:
			y2=mem->GetDrop(n);
			break;
		case PT_ENERGY:
			y2=mem->GetEnergy(n);
			break;
		case PT_VELOCITY:
			y2=mem->GetVelocity(n);
			break;
		case PT_HVELOCITY:
			y2=mem->GetVx(n);
			break;
		case PT_WINDAGE:
			y2=mem->GetWindage(n);
			break;
	}
	
	// Translate the x,y values into scaled pixels.
	px1=(int)(xmin+x_scale*(double)x1);
	px2=(int)(xmin+x_scale*(double)x2);
	
	py1=(int)((y0)-(double)y_scale*(double)y1);
	py2=(int)((y0)-(double)y_scale*(double)y2);
	
	if (py2>=ymax) break;
	if (px2>=xmax) break;
	
	// Plot the points.
	if (px1>xmin && px1<xmax && py1<ymax && py1>ymin)
		fl_line(px1,py1,px2,py2);
	
	x1=x2;
	y1=y2;
	}

}


/*
   SetupPlot()
   
   Calculate some common parameters for the plots
   This code was common to all plots (Path, Energy, Velocity) and was consolidated here
   
   Requires x_range and y_range to be set before calling
*/

void PlotWindow::SetupPlot(void){

	int w = this->w();
	int h = this->h();
	
	xmin = 5;
	xmax = w-5;
	ymin = 100;
	ymax = h-5;

	x_scale = (double)(xmax-xmin) / (double)x_range;
	y_scale = (double)(ymax-ymin) / (double)y_range;
	
	// The gridlines look best with 15-25 graduations in x, so we set some basic defaults here.
	x_ticks=10;
	if (sl_x->value() > 100) x_ticks=25;
	if (sl_x->value() > 500) x_ticks=50;
	if (sl_x->value() > 1000) x_ticks=100;
	if (sl_x->value() > 2000) x_ticks=250;
	if (sl_x->value() > 4000) x_ticks=500;

	// The y-axis looks best with about 7-12 ticks, so we scale it here reasonably.
	y_ticks=1;
	if (y_range > 10) y_ticks=2;
	if (y_range > 20) y_ticks=6;
	if (y_range > 40) y_ticks=12;
	if (y_range > 100) y_ticks=24;
	if (y_range > 200) y_ticks=48;
	if (y_range > 400) y_ticks=100;
	if (y_range > 800) y_ticks=250;
	if (y_range > 1600) y_ticks=500;
	if (y_range > 3200) y_ticks=1000;
	if (y_range > 6400) y_ticks=2000;
	if (y_range > 12800) y_ticks=4000;
	if (y_range > 25600) y_ticks=8000;
	if (y_range > 51200) y_ticks=16000;
	if (y_range > 102400) y_ticks=32000;
	if (y_range > 204800) y_ticks=64000;

}


/*
   DrawPlotBackground()
   
   Draw the graph background, axis, scale etc
   Code was common to all plots and has been consolidated here
   
   Requires SetupPlot to have been called and y0 set first
*/

void PlotWindow::DrawPlotBackground(int mode, const char *ylabel){
	// Now do our custom drawing.
	fl_draw_box(FL_FLAT_BOX,xmin,ymin,w()-10,h()-ymin-5,FL_WHITE);

	// Draw the x-axis.
	fl_color(FL_BLACK);
	fl_line_style(FL_SOLID,2);
	fl_line(xmin,y0,xmax,y0);

	double px;
	fl_color(FL_BLACK);
	fl_line_style(FL_SOLID,1);
	char txt[64];
	int r=0;
	double txtw=0;

	int tick_top=ymax;
	if (mode != PT_PATH && mode != PT_DROP && mode != PT_WINDAGE) tick_top=y0;

	// Draw X gridlines every x_ticks yds and label them.
	for (px=xmin;px<xmax;px+=((double)x_ticks*x_scale)){
		//fl_line((int)px,y_bottom,(int)px,y_top);
		fl_line_style(FL_DOT,1);
		fl_line((int)px,(int)ymin,(int)px,tick_top);
		sprintf(txt,"%d",r*x_ticks);
		if (r%2==0 && r>0) {
			txtw=fl_width(txt);
			fl_line_style(FL_SOLID,1);
			fl_line((int)px,y0,(int)px,y0+8);
			fl_draw(txt,(int)((int)px-(txtw/2)),y0+10+fl_height());
		}
		r++;
	}
	
	// Draw Y hashmarks every 1 y_tick going up.
	double py;
	r=0;
	for (py=y0;py>=ymin;py-=((double)y_ticks*y_scale)){
		if (r>0){
			fl_line(xmin,(int)py,xmax,(int)py);
			sprintf(txt,"+%d%s",r*y_ticks, ylabel);
			fl_draw(txt,xmin+10,(int)(py+fl_height()/2));
		}
		r++;
	}
	
	// Draw Y hashmarks every 1 y_tick going down
	if (mode==PT_PATH || mode==PT_DROP || mode==PT_WINDAGE) {
	   r=0;
	   for (py=y0;py<=ymax;py+=((double)y_ticks*y_scale)){
		if (r>0){
			fl_line(xmin,(int)py,xmax,(int)py);
			sprintf(txt,"-%d%s",r*y_ticks, ylabel);
			if (py+fl_height()/2<ymax)	fl_draw(txt,xmin+10,(int)(py+fl_height()/2));
		}
		r++;
	   }
	}

}


void PlotWindow::DrawPlotData(int mode){

	fl_color(FL_BLUE);
	fl_line_style(FL_SOLID,1);
	PlotMem(this->gsln, mode);
	
	if (btMem1->value()==1){
		fl_color(FL_RED);
		fl_line_style(FL_SOLID,1);
		PlotMem(mem1, mode);
		}
	if (btMem2->value()==1){
		fl_color(FL_GREEN);
		fl_line_style(FL_SOLID,1);
		PlotMem(mem2, mode);
		}
	if (btMem3->value()==1){
		fl_color(FL_BLACK);
		fl_line_style(FL_SOLID,1);
		PlotMem(mem3, mode);
		}
	
	if (x_click>xmin && x_click<xmax && y_click>ymin && y_click<ymax){
		// If the user clicks, show the coordinates they clicked on.
		double click_label_x= (x_click-xmin)/x_scale;
		double click_label_y=(y0-y_click)/y_scale;
		char lbl_point[20];
		sprintf(lbl_point,"(%.0f,%.2f\",%.1fcm)",
		      click_label_x, click_label_y, click_label_y * 2.54);
		fl_color(FL_BLACK);
		fl_draw(lbl_point,x_click+5, y_click-5);
		fl_rectf(x_click-2, y_click-2,4,4);
	}

	// Draw the frame around it last to make it all pretty.
	fl_frame("aaaa",5,100,w()-10,h()-105);

}


void PlotWindow::PlotPath(int ptype){

	// We need to find the max and min y-values to determine our y scale and y-ticks
	double _miny=0;
	double _maxy=0;
	
	if (ptype == PT_WINDAGE) {
	   for (int e=0;e<sl_x->value();e++){
		if (gsln->GetWindage(e) > _maxy) _maxy=gsln->GetWindage(e);
		if (gsln->GetWindage(e) < _miny) _miny=gsln->GetWindage(e);
	   }
	} else if (ptype == PT_DROP) {
	   for (int e=0;e<sl_x->value();e++){
		if (gsln->GetDrop(e) > _maxy) _maxy=gsln->GetDrop(e);
		if (gsln->GetDrop(e) < _miny) _miny=gsln->GetDrop(e);
	   }
	} else {
	   for (int e=0;e<sl_x->value();e++){
		if (gsln->GetPath(e) > _maxy) _maxy=gsln->GetPath(e);
		if (gsln->GetPath(e) < _miny) _miny=gsln->GetPath(e);
	   }
	}

	x_range=(int)sl_x->value();
	
	_miny*=1.1;
	if (_miny > -4) _miny=-4;
	if (_maxy < 3) _maxy=3;
	else if (_maxy < 10) _maxy *= 2;
	else _maxy*=1.3;
	
	if (_maxy < fabs(_miny)/3) _maxy = fabs(_miny)/3;
	else if (fabs(_miny) < (_maxy/3)) _miny = -_maxy/3;
	
	y_range = _maxy - _miny;
	
	SetupPlot();
	y0 = ymax + (_miny * y_scale);

	DrawPlotBackground(ptype, "\"");

	// draw +/- 1 and 5 MOA or MIL lines
	if (cScopeGuides->value() > 0) {
	   int sgmode=cScopeGuides->value();
	   double pix_inset=50;

	   // one MIL - not quite equal to one real milliradian
	   long double oneunit = 3.1415 / 3200;
	   
// convert mm@100m to one step unit on plot
#define digisight_unit(mm) (((mm*0.9144)/25.4)*(oneunit / 3.438))

	   if (sgmode >= 13)
	   	oneunit=digisight_unit(17); // 17mm@100m (One Digisight N960 click)
	   else if (sgmode >= 11)
	   	oneunit=digisight_unit(13); // 13mm@100m (One Digisight N750 click)
	   else if (sgmode >= 9)
	        oneunit=0.0174532925; // use one degree
	   else if (sgmode >= 5)
	        oneunit=oneunit / 3.438; // use one moa instead
	   
	   double range_inset=pix_inset/x_scale;
	   double near_mark, far_mark;
	   int start, stop, step, step2, g;
	   char txt[10];
	   
	   step2=0;
	   switch (sgmode) {
	      case 1: start=1; stop=5;  step=4; break;		 // 1,5 MIL
	      case 2: start=1; stop=5;  step=1; break;		 // 1-5 MIL
	      case 3: start=1; stop=10; step=1; break;		 // 1-10 MIL
	      case 4: start=1; stop=5;  step=1; step2=5; break;	 // 1-5,10 MIL
	      case 5: start=2; stop=10; step=8; break;		 // 2,10 MOA
	      case 6: start=2; stop=10; step=2; break;		 // 2,4,6,8,10 MOA
	      case 7: start=2; stop=20; step=2; step2=10; break; // 2-20,30 MOA
	      case 8: start=2; stop=10; step=2; step2=10; break; // 2,4,6,8,10,20 MOA
	      case 9:  start=1; stop=5; step=1; break;		 // 1-5 Degrees
	      case 10: start=5; stop=15; step=5; break;		 // 5,10,15 Degrees
	      case 11: start=10; stop=50; step=10; break;	 // 10,20,30,40,50 Digisight clicks
	      case 12: start=10; stop=90; step=10; break;	 // 10,20..90 Digisight clicks
	      case 13: start=10; stop=50; step=10; break;	 // 10,20,30,40,50 Digisight clicks
	      case 14: start=10; stop=90; step=10; break;	 // 10,20..90 Digisight clicks
	      
	      default: start=1; stop=1; step=1; break;
	      }

	   fl_clip(5,100,w()-10,h()-105);
	   for (g=start; g<=stop; g+=step) {
	       near_mark=tanf(oneunit*g) * range_inset * 36.0; // and convert yards->inches
	       far_mark =tanf(oneunit*g) * (x_range - range_inset) * 36.0;
	       
	       if (near_mark < y_range/2) {
	          fl_line(xmin+pix_inset,y0-(near_mark*y_scale),xmax-pix_inset,y0-(far_mark*y_scale));
	          if (g==start) {
	             if (sgmode>=13) sprintf(txt, "%d N960", g);
	             else if (sgmode>=11) sprintf(txt, "%d N750", g);
	             else if (sgmode>=9) sprintf(txt, "%d Deg", g);
	             else if (sgmode>=5) sprintf(txt, "%d MOA", g);
	             else sprintf(txt, "%d MIL", g);
	             }
	          else sprintf(txt, "%d", g);
	          fl_draw(txt, xmax-pix_inset+3, y0-(far_mark*y_scale));
	       
	          fl_line(xmin+pix_inset,y0+(near_mark*y_scale),xmax-pix_inset,y0+(far_mark*y_scale));
	          if (g==start) {
	             if (sgmode>=13) sprintf(txt, "-%d N960", g);
	             else if (sgmode>=11) sprintf(txt, "-%d N750", g);
	             else if (sgmode>=9) sprintf(txt, "-%d Deg", g);
	             else if (sgmode>=5) sprintf(txt, "-%d MOA", g);
	             else sprintf(txt, "-%d MIL", g);
	             }
	          else sprintf(txt, "-%d", g);
	          fl_draw(txt, xmax-pix_inset+3, y0+(far_mark*y_scale));
	          }
	       }
	   if (step2 > 0) {
	       g+=step2-step;
	       near_mark=tanf(oneunit*g) * range_inset * 36.0;
	       far_mark =tanf(oneunit*g) * (x_range - range_inset) * 36.0;

	       if (near_mark < y_range/2) {
	          fl_line(xmin+pix_inset,y0-(near_mark*y_scale),xmax-pix_inset,y0-(far_mark*y_scale));
	          sprintf(txt, "+%d", g);
	          fl_draw(txt, xmax-pix_inset+3, y0-(far_mark*y_scale));
	       
	          fl_line(xmin+pix_inset,y0+(near_mark*y_scale),xmax-pix_inset,y0+(far_mark*y_scale));
	          sprintf(txt, "-%d", g);
	          fl_draw(txt, xmax-pix_inset+3, y0+(far_mark*y_scale));
	          }
	       }
	      
	   fl_pop_clip();
	   }
	   
	DrawPlotData(ptype);

	return;
}

void PlotWindow::PlotEnergy(void){

	// We need to find the max y-value to determine our y scale and y-ticks

	double _maxy=0;
	double _miny=0;
		
	for (int e=1;e<sl_x->value();e++){
		if (gsln->GetPath(e) > _maxy) _maxy=gsln->GetEnergy(e);
	}

	x_range=(int)sl_x->value();
	
	if (fabs(_miny) > fabs(_maxy))
	     y_range = fabs(1.5*_miny);
	else y_range = fabs(1.5*_maxy);

	SetupPlot();
	y0 = (ymax)-50;

	DrawPlotBackground(PT_ENERGY, " ft-lbs");
	DrawPlotData(PT_ENERGY);
	
	return;
}

void PlotWindow::PlotVelocity(int ptype){

	// We need to find the max y-value to determine our y scale and y-ticks

	double _maxy=0;
	double _miny=0;
		
	for (int e=1;e<sl_x->value();e++){
		if (gsln->GetPath(e) > _maxy) _maxy=gsln->GetVelocity(e);
	}

	x_range=(int)sl_x->value();

	if (fabs(_miny) > fabs(_maxy)) 
	     y_range = fabs(1.5*_miny);
	else y_range = fabs(1.5*_maxy);

	SetupPlot();
	y0 = (ymax)-50;

	DrawPlotBackground(PT_VELOCITY, " ft/s");
	DrawPlotData(ptype);

	return;
}

void PlotWindow::cb_btPlot(Fl_Widget* o, void* v){
	PlotWindow* T = (PlotWindow*)v;
	
	T->damage(255);
	T->draw();


}

void PlotWindow::cb_nothing(Fl_Widget* o, void* v){

	;
}

void PlotWindow::cb_Close(Fl_Widget* o, void* v){
	PlotWindow* T = (PlotWindow*)v;
	T->~PlotWindow();
}

void PlotWindow::cb_slUpdate(Fl_Widget* o, void* v){

	PlotWindow* T = (PlotWindow*)v;
	T->damage(255);
	T->draw();
}

void PlotWindow::cb_Mem1(Fl_Widget* o, void* v){

	PlotWindow* T = (PlotWindow*)v;
	T->damage(255);
	T->draw();

}


void PlotWindow::cb_Mem2(Fl_Widget* o, void* v){

	PlotWindow* T = (PlotWindow*)v;
	T->redraw();

}

void PlotWindow::cb_Guides(Fl_Widget* o, void* v){

	PlotWindow* T = (PlotWindow*)v;
	T->redraw();

}

void PlotWindow::cb_PlotType(Fl_Widget* o, void* v){

	PlotWindow* T = (PlotWindow*)v;
	T->redraw();

}


